#include "faketcp.h"
#include "common/bytetoint.h"
#include "common/inttobyte.h"

QMutex FakeTcp::socketsMutex;
QMap<QPair<QString, quint16>, FakeTcp*> FakeTcp::availableSockets;

FakeTcp::FakeTcp(QString destination, quint16 port, bool bindPort, quint16 localPort) : destination(destination), port(port), localPort(localPort) {
	connect(&(this->socket), SIGNAL(readyRead()), this, SLOT(readData()));
	//Start sending messages thread
	sendThread = new SendThread(this, &packetMutex, &packetCondition, &sendingPackets);
	sendThread->start();

	//Insert itself into receiving sockets
	socketsMutex.lock();
	availableSockets.insert(qMakePair(destination, port), this);
	socketsMutex.unlock();

	//Listen to port.
	if(bindPort) while(this->socket.bind(this->localPort) == false) this->localPort++;
}

FakeTcp::~FakeTcp() {
	//Remove itself from receiving sockets
	socketsMutex.lock();
	availableSockets.remove(qMakePair(destination, port));
	socketsMutex.unlock();
	delete sendThread;
}

void FakeTcp::sendMessage(QString message) {
	Packet* packet;
	unsigned int currentCode;
	const unsigned int partSize = 495;

	currentCode = messageCode;
	messageCode += 1;

	QList<QString> parts;

	//Divide message into parts
	while(message.size() > 0) {
		QString part = message.left(partSize);
		parts.push_back(part);
		message.remove(0, partSize);
	}

	//Create message packet
	packet = new Packet();
	unsigned int numberParts = parts.size();

	packetMutex.lock();
	
	for(unsigned int i = 0; i < numberParts; i++) packet->addPart(this->packetData(this->localPort, i + 1, numberParts, currentCode, 0, parts[i]));
	//Add packet to sending queue and warn thread.
	sendingPackets.push_back(packet);

	packetMutex.unlock();
	packetCondition.wakeAll();
}

//True if there is any message ready to be read
bool FakeTcp::hasReadyMessage() {
	return !(this->readyMessages.isEmpty());
}

//Erase first message from ready list and return it.
QString FakeTcp::getMessage() {
	if(this->hasReadyMessage()) {
		QString result = this->readyMessages[0].first;
		this->readyMessages.removeFirst();

		return result;
	} else
		return "";
}

//Erase first message from ready list and return it along with ip and port where it came from.
QString FakeTcp::getMessage(QString& ip, quint16& port) {
	if(this->hasReadyMessage()) {
		QString result = this->readyMessages[0].first;
		QString fromIp = this->readyMessages[0].second.first;
		quint16 fromPort = this->readyMessages[0].second.second;
		ip = fromIp;
		port = fromPort;

		this->readyMessages.removeFirst();

		return result;
	} else
		return "";
}

//Send a part to the destination.
void FakeTcp::sendPart(QByteArray data) {
	socket.writeDatagram(data, QHostAddress(destination), port);
}

QString FakeTcp::getDestination() {
	return destination;
}

quint16 FakeTcp::getPort() {
	return port;
}

void FakeTcp::readData() {
	while(this->socket.hasPendingDatagrams()) {
		QHostAddress address;
		QByteArray data;
		data.resize(this->socket.pendingDatagramSize());

		//read UDP datagram and get address
		this->socket.readDatagram(data.data(), data.size(), &address);

		while(data.size() > 0) {
			//Extract information from datagram
			if(data.size() < 17) data.remove(0, data.size());

			quint16 answerPort = byteToUInt16(data.left(2));
			data.remove(0, 2);

			quint32 partNumber = byteToUInt32(data.left(4));
			data.remove(0, 4);
			
			quint32 totalParts = byteToUInt32(data.left(4));
			data.remove(0, 4);
			
			quint32 packetCode = byteToUInt32(data.left(4));
			data.remove(0, 4);
			
			quint16 packetSize = byteToUInt16(data.left(2));
			data.remove(0, 2);

			char flags = data[0];
			data.remove(0, 1);
			bool ACK = ((flags & 0x01) != 0);

			if(ACK == false) { //If is a message
				char sendFlags = 1;
				//Answer ACK
				QByteArray returnData = this->packetData(this->localPort, partNumber, totalParts, packetCode, sendFlags, "");
				this->socket.writeDatagram(returnData, address, answerPort);
			
				QString message = QString(data.left(packetSize));
				data.remove(0, packetSize);

				QPair<quint32, QPair<QString, quint16> > packetInfo;

				//Store it
				if(this->incompletePackets.contains(packetInfo) == false) {
					this->incompletePackets.insert(packetInfo, new PacketData(totalParts));
				}

				PacketData * packet = this->incompletePackets[packetInfo];
				
				packet->addPart(partNumber, message);
				
				if(packet->isComplete()) { //if packet is complete, warn user
					this->readyMessages.push_back( qMakePair(packet->getMessage(), qMakePair(address.toString(), answerPort)) );
					this->incompletePackets.remove(packetInfo);
					delete packet;
					packet = 0;
					emit(messageReady());
				} else { //if packet should be complete but it's not, delete it.
					if(partNumber == totalParts) {
						this->incompletePackets.remove(packetInfo);
						delete packet;
						packet = 0;
					}
				}
			} else { //If it's an ACK message, pass it to the correct socket.
				socketsMutex.lock();
				FakeTcp* sendingSocket = availableSockets.value(qMakePair(address.toString(), answerPort));
				sendingSocket->sendThread->sendAck();
				socketsMutex.unlock();
			}
		}
	}
}

//Package data into one bytearray for sending.
QByteArray FakeTcp::packetData(quint16 answerPort, quint32 partNumber, quint32 totalParts, quint32 packetCode, char flags, QString message) {
	QByteArray result;
	result.append(uint16ToByte(answerPort));
	result.append(uint32ToByte(partNumber));
	result.append(uint32ToByte(totalParts));
	result.append(uint32ToByte(packetCode));
	result.append(uint16ToByte((quint16) message.size()));
	result += flags;
	result += message;

	return result;
}
